﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Exception Handling Application Block
//===============================================================================

using System;
using System.Data.SqlClient;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using EntLibContrib.ExceptionHandling.Configuration;

namespace EntLibContrib.ExceptionHandling
{
	/// <summary>
	/// Exception Handler that can map a SqlException with nested SqlError objects to custom .NET exceptions specified in configuration.
	/// </summary>
	[ConfigurationElementType(typeof(SqlExceptionWrapHandlerData))]
	public class SqlExceptionWrapHandler : IExceptionHandler
	{
		#region Fields
		private ErrorCodeNotConfiguredBehavior errorCodeNotConfiguredBehavior;
		private string defaultExceptionMessage;
		private Type defaultExceptionType;
		private NamedElementCollection<ErrorCodeEntryData> errorCodeEntries;
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new instance of the <see cref="SqlExceptionWrapHandler"/>.
		/// </summary>
		/// <param name="configuration">The configuration object used to set the runtime values</param>
		public SqlExceptionWrapHandler(SqlExceptionWrapHandlerData configuration)
		{
			this.errorCodeNotConfiguredBehavior = configuration.ErrorCodeNotConfiguredBehavior;
			this.defaultExceptionMessage = configuration.DefaultExceptionMessage;
			this.defaultExceptionType = configuration.DefaultWrapExceptionType;
			this.errorCodeEntries = configuration.ErrorCodes;
		}
		#endregion

		#region IExceptionHandler Members

		/// <summary>
		/// Handles the exception.
		/// </summary>
		/// <param name="exception">The original exception.</param>        
		/// <param name="handlingInstanceId">The unique identifier attached to the handling chain for this handling instance.</param>
		/// <returns>Modified exception to pass to the next exceptionHandlerData in the chain.</returns>
		public Exception HandleException(Exception exception, Guid handlingInstanceId)
		{
			SqlException sqlException = exception as SqlException;
			if (sqlException == null)
			{
				throw new ExceptionHandlingException("The SqlExceptionWrapHandler can only operate on SqlException types.");
			}

			ErrorCodeEntryData matchingEntry = null;
			// Iterate through all of the SQL Errors 
			foreach (SqlError error in sqlException.Errors)
			{
				// Iterate through the configured error codes to see if any match
				foreach (ErrorCodeEntryData entry in errorCodeEntries)
				{
					if (entry.ErrorCode == error.Number)
					{
						matchingEntry = entry;
						break;
					}
				}

				// If we've found a matching error code, break out
				if (matchingEntry != null)
				{
					break;
				}
			}

			if (matchingEntry == null)
			{
				// no mapping exists
				if (errorCodeNotConfiguredBehavior == ErrorCodeNotConfiguredBehavior.WrapWithDefaultException)
				{
					return WrapException(exception, defaultExceptionType, defaultExceptionMessage);
				}
				else
				{
					return exception;
				}
			}
			else
			{
				// Mapping was found, so return the exception specified in configuration
				return WrapException(exception, matchingEntry.WrapExceptionType, matchingEntry.ExceptionMessage);
			}
		}

		#endregion

		#region Private Static Methods
		private static Exception WrapException(Exception originalException, Type wrapExceptionType, string wrapExceptionMessage)
		{
			object[] extraParameters = new object[2];
			extraParameters[0] = wrapExceptionMessage;
			extraParameters[1] = originalException;
			return (Exception)Activator.CreateInstance(wrapExceptionType, extraParameters);
		}
		#endregion
	}
}
